package org.nicocube.airain.domain.server.data;

import java.util.List;

import org.nicocube.airain.domain.client.data.Storable;
import org.nicocube.airain.domain.client.data.StorageException;
import org.nicocube.airain.domain.server.criteria.Criteria;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.query.Query;

/**
 * Utility class to manage Storage of a type of data
 *  
 * @author nicolas
 * @version 0.1
 *
 * @param <T>
 */
public class Storage<T extends Storable> {
	private static final Logger log = LoggerFactory.getLogger(Storage.class);
	
	protected Class<T> clazz;

	protected ContainerProvider containerProvider;
	
	protected Storage(Class<T> clazz,ContainerProvider containerProvider) {
		this.clazz = clazz;
		this.containerProvider = containerProvider;
	}

	
	protected ObjectContainer getContainer() {
		return containerProvider.getContainer();
	}
	
	/**
	 * Save the given parameter in the container
	 * 
	 * @param t
	 * @return the {@link Storage} itself for fluent-style programming
	 * @throws StorageException
	 */
	public Storage<T> save(T t) throws StorageException {
		getContainer().store(t);
		return this;
	}

	/**
	 * Delete the given parameter in the container
	 * @param t
	 * @return the {@link Storage} itself for fluent-style programming
	 */
	public Storage<T> delete(T t) {
		getContainer().delete(t);
		return this;
	}
	
	/**
	 * Build query constrained on the inner {@link Storage#clazz} for the given criterias
	 * 
	 * @param criterias the criterias we search object for
	 * @return
	 */
	protected Query queryBuilder(Criteria ... criterias) {
		if (log.isDebugEnabled()) log.debug("queryBuilder:"+criterias.length);
		Query q = getContainer().query();
		q.constrain(clazz);
		for (Criteria crit : criterias)
			crit.apply(q);
		return q;
	}
	
	/**
	 * Retrieve a List of object for the given criterias
	 * 
	 * @param criterias
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public List<T> retrieve(Criteria ... criterias) {
		return queryBuilder(criterias).execute();
	}

	/**
	 * Retrieve one object for the given criterias
	 * 
	 * @param criterias
	 * @return
	 */
	public T retrieveOne(Criteria ... criterias) {
		Query q = queryBuilder(criterias);
		@SuppressWarnings("unchecked") ObjectSet<T> r = q.execute();
		if (!r.isEmpty()) return clazz.cast(r.next());
		else return null;
	}

	/**
	 * Verify if an object exists for the given criterias
	 * 
	 * @param criterias
	 * @return
	 */
	public boolean exists(Criteria ... criterias) {
		Query q = queryBuilder(criterias);
		return ! q.execute().isEmpty();
	}
	
	/**
	 * 
	 * @return
	 */
	public Storage<T> commit() {
		getContainer().commit();
		return this;
	}
	
	/**
	 * 
	 * @return
	 */
	public Storage<T> rollback() {
		getContainer().rollback();
		return this;
	}

	/**
	 * 
	 * @param obj
	 * @return
	 */
	public Storage<T> refresh(T obj) {
		getContainer().ext().refresh(obj, 4);
		return this;
	}

}
